package pwd.allen.simple.handler;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;

/**
 * 自定义一个handler需要继承netty规定好的某个handlerAdapter
 * 实现功能：打印接收到的消息并返回祖安人的问候
 *
 * @author 门那粒沙
 * @create 2019-10-27 18:25
 **/
public class SimpleServerHandler extends ChannelInboundHandlerAdapter {

    //创建业务线程池，可以将任务提交到该线程池
    private static EventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(10);

    /**
     * 读取客户端发送的消息
     *
     * @param ctx 上下文对象，含有管道pipeline（给handler处理数据）、通道channel（传输数据）、地址
     * @param msg 客户端发送的数据，默认Object
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //每个socketChannel注册在workerGroup的其中一个EventLoop的selector里
        System.out.println(String.format("【%s】ctx:%s", Thread.currentThread().getName(), ctx));

        String strMsg = null;
        if (msg instanceof ByteBuf) {//如果前面没有handler处理，msg为最初的类型ByteBuf
            //ByteBuf是netty提供的，性能比ByteBuffer更高
            ByteBuf buf = (ByteBuf) msg;
            strMsg = buf.toString(CharsetUtil.UTF_8);
        } else {
            strMsg = msg.toString();
        }

        Channel channel = ctx.channel();
        ChannelPipeline pipeline = ctx.pipeline();//本质是一个双向链接, 出站入站
        ChannelHandler handler = ctx.handler();//和当前handler是同一个对象

        System.out.println(String.format("接收到客户端(%s)的消息：%s"
                , channel.remoteAddress(), strMsg));


        //可以把耗时的业务提交该channel 的 NIOEventLoop 的 taskQueue中，异步按顺序先进先出地执行
        //方案1 用户程序自定义的普通任务，如果是ctx.channel().eventLoop().execute方式实际上还是同个线程处理，仍然会阻塞
        ctx.channel().eventLoop().execute(() -> {
            try {
                Thread.sleep(5 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("任务提交到当前channel所在的eventExecutor, 过了5秒了", CharsetUtil.UTF_8));
                System.out.println(String.format("【%s】channel code=%s", Thread.currentThread().getName(), ctx.channel().hashCode()));
            } catch (Exception ex) {
                System.out.println("发生异常" + ex.getMessage());
            }
        });
        eventExecutorGroup.submit(() -> {
            try {
                System.out.println(String.format("【%s】channel code=%s", Thread.currentThread().getName(), ctx.channel().hashCode()));
                Thread.sleep(5 * 1000);
                //writeAndFlush方法会判断当前executor是否当前线程，不是的话会封装成task放入mpsc 队列中，等待 IO 任务执行完毕后执行队列中的任务
                ctx.writeAndFlush(Unpooled.copiedBuffer("任务提交到DefaultEventLoopGroup, 过了5秒了", CharsetUtil.UTF_8));
            } catch (Exception ex) {
                System.out.println("发生异常" + ex.getMessage());
            }
        });

        //方案2 : 用户自定义定时任务 -》 该任务是提交到 scheduleTaskQueue中
//        ctx.channel().eventLoop().schedule(new Runnable() {
//            @Override
//            public void run() {
//
//                try {
//                    ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 这是个5秒后执行的定时任务", CharsetUtil.UTF_8));
//                    System.out.println("channel code=" + ctx.channel().hashCode());
//                } catch (Exception ex) {
//                    System.out.println("发生异常" + ex.getMessage());
//                }
//            }
//        }, 5, TimeUnit.SECONDS);

    }

    /**
     * 数据读取完毕
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //将数据写入缓存，并刷新（把消息写到管道发送）
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello!客户端", CharsetUtil.UTF_8));
    }

    /**
     * 捕获异常
     * 一般需要关闭通道
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("出错了：" + cause.toString());
        //关闭通道
        ctx.close();
    }

    /**
     * 触发用户事件时回调
     * @param ctx 上下文
     * @param evt 事件
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

        if(evt instanceof IdleStateEvent) {//处理心跳检测
            //将  evt 向下转型 IdleStateEvent
            IdleStateEvent event = (IdleStateEvent) evt;
            String eventType = null;
            switch (event.state()) {
                case READER_IDLE:
                    eventType = "读空闲";
                    break;
                case WRITER_IDLE:
                    eventType = "写空闲";
                    break;
                case ALL_IDLE:
                    eventType = "读写空闲";
                    break;
            }
            System.out.println(String.format("【%s】%s", eventType, ctx.channel().remoteAddress()));

            //如果发生空闲，我们关闭通道
//             ctx.channel().close();
        }
    }
}
